import $ from 'jquery';
import _ from 'underscore';
import moment from 'moment';
import numeral from 'numeral';
import { fromJS, List, is } from 'immutable';
import EventProxy from 'eventproxy';

import format from 'lib/util/format';
import { Scope } from 'lib/scopeFactory';
import queryStore from 'lib/util/QueryStore.es6';
import { Info } from 'lib/components/Notify.jsx';
import { Warning } from 'lib/components/Notify.jsx';
import Operators from 'lib/util/FilterConditionOperators';

import { getDayFromAlias, getDayFromFunction } from '../common/DateRangeStoreEnhanced.es6';
import { defaultApi, request2, defaultPostApi } from '../apis/core-util';

import {
  nullFilterValue,
  oneDayValueOf,
  anythingEvent,
  defaultEventIndicator,
  defaultOptionExtensions,
  defaultEventIndicatorItems,
  totalDimension,
  defaultViewDimension,
  defaultViewDimensionNumberSettins,
  defaultChartColors,
  defaultComparedChartColors
} from './Constants.es6';

_.mixin({
  intersect: (left, right, func) => _.filter(left, x => _.some(right, y => func(x, y)))
});

export default class RootScope extends Scope {
  constructor(props) {
    super(props);

    this.ep = EventProxy.create();

    let self = this;
    this.legendSetting = () => {
      let staggerLines = 1;
      if (this.$state.chart0.data || this.$state.chartPie0.data) {
        let data = this.$state.chart0.data || this.$state.chartPie0.data;
        if (data.series && data.series.length && data.series[0].data.length > 24) {
          staggerLines = 2;
        }
      }
      // let staggerLines = self.$state.chart0Settings.dateType === 'hour' ? 2 : 1;
      return {
        legend: {
          itemStyle: {
            fontSize: '12px',
            width: 'auto',
            textOverflow: 'initial',
            overflow: 'auto',
            whiteSpace: 'initial'
          }
        },
        xAxis: {
          labels: {
            staggerLines: staggerLines,
            formatter: function () {
              // debugger

              let index = this.axis.tickPositions.indexOf(this.value);
              if (index === -1) {
                return null;
              }

              let isMain = this.axis.series[0].xData.indexOf(this.value);
              if (isMain === -1) {
                return moment(this.value).format('HH:mm');
              }

              let multiYear = _.chain(this.axis.series)
                              .map(p => p.options.data)
                              .flatten(true)
                              .map(p => moment(p.x))
                              .groupBy(p => p.year())
                              .size()
                              .value() !== 1;

              let datetime = null;
              switch(self.$state.chart0Settings.dateType) {
                case 'hour':
                  datetime = p => multiYear ? moment(p).format('YYYY-MM-DD HH:mm') : moment(p).format('MM-DD HH:mm');
                  break;
                case 'day':
                  datetime = p => multiYear ? moment(p).format('YYYY-MM-DD') : moment(p).format('MM-DD');
                  break;
                case 'month':
                  datetime = p => multiYear ? moment(p).format('YYYY-MM') : moment(p).format('MM') + '月';
                  break;
                default:
                  datetime = p => `${ moment(p).format('YYYY-MM-DD') } ~ ${ moment(p).add(6, 'days').format('YYYY-MM-DD') }`;
                  break;
              }

              if (self.$state.showCopmarer) {
                return _.chain(this.axis.series)
                        .map(p => p.options)
                        .groupBy(p => p.dashStyle)
                        .map(p => _.find(p[0].data, r => r.x === this.value))
                        .map(p => datetime(p.labelSerie))
                        .value()
                        .join('/');
              } else {
                return datetime(this.value);
              }
            }
          }
        }
      };
    };

    this.tooltipFormat = function (tooltip) {
      let template = `<span style="color:<%= point.color %>; max-width: 320px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; display: inline-block;">${ this.point.name }</span><span style="display: inline-block; overflow: hidden">：${ this.point.customData.y.yValue } 占 <b>${ this.point.customData.y.fmtVal }</b></span>`;

      return _.template(template)({
        serie:this.series,
        point:this.point
      });
    }; // tooltipFormat

    this.tooltipFormatLine = function (tooltip) {
      let { label } = this.point.customData.y;
      label += this.point.suffix;

      let datetime = '';
      switch(self.$state.chart0Settings.dateType) {
        case 'hour':
          datetime = moment(this.point.labelSerie).format('YYYY-MM-DD HH:mm');
        break;
        case 'day':
          datetime = moment(this.point.labelSerie).format('YYYY-MM-DD');
        break;
        case 'month':
          datetime = moment(this.point.labelSerie).format('M') + '月';
        break;
        default:
          datetime = `${ moment(this.point.labelSerie).format('YYYY-MM-DD') } ~ ${ moment(this.point.labelSerie).add(6, 'days').format('YYYY-MM-DD') }`;
        break;
      }

      let template = `<span>${ datetime }</span><br /><span style="color:<%= point.color %>; max-width: 320px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; display: inline-block;">${ label }</span><b style="display: inline-block; overflow: hidden">${ '：' + this.point.customData.y.fmtVal }</b>`;

      return _.template(template)({
        serie:this.series,
        point:this.point
      });
    }; // tooltipFormat

    this.$state = {
      showEventTableType: 'current',
      showCopmarer: false,
      favoriteId: null,
      favoriteName: null,
      events: {
        loading: true
      },
      properties: {},
      intersectProperties: null,
      propertySuggests: {},
      viewDimensionOptions: {
        loading: true
      },
      filterConditionOptions: {
        loading: true
      },
      basicData: {
        eventIndicatorNames: [],
        viewDimensionDic: {}
      },
      fetchedData: {
        grid: {
          loading: true
        },
        sum: {
          loading: true
        }
      },
      data: {
        eventIndicators: [],
        viewDimensions: [],
        filterConditions: null
      },
      date: {
        isNatural: true,
        isCustom: false,
        alias: '0 week',
        picker: {
          endTime: moment().startOf('day').valueOf(),
          spanTime: moment().startOf('day').valueOf() - moment().startOf('week').valueOf()
        },
        comparer: null
      },
      chart0Settings: {
        showChart0: true,
        char0Name: null,
        unit: null,
        chartType: 'line',
        settings: {},
        pieChart0Key: 0,
        dateType: 'day',
        viewDimensions: '',
        percent: 64
      },
      chart0: {
        loading: true
      },
      chartPie0: {
        loading: true
      },
      showSelectedColumns: false,
      showDataGridTotal: false,
      selectedColumns: [],
      dataGridColumns: [],
      dataGrid: {
        loading: true
      }
    }; // $state

    this.eventsApi = defaultApi({
      __url: `sd/${ BlueWare.appId }/api/events/all`
    });

    this.suggestApi = request2.bind(null, {
      body: {
        limit: 19
      }
    });

    this.chartApi = defaultPostApi({
      __url: 'api/events/report/',
      bookmarkId: null
    });
  } // constructor

  $remove_indicator_value({ event, index }) {
    let target = _.find(this.$state.data.eventIndicators, p => p.event === event);
    if (!target) {
      return;
    }

    if (target.indicators.length === 1) {
      if (this.$state.data.eventIndicators.length === 1) {
        Info('最后一项事件指标不能删除');
        return;
      } else {
        let targetIndex = _.findIndex(this.$state.data.eventIndicators, p => p.event === event);

        this.removeEventTrigger(targetIndex);

        this.$state.data.eventIndicators = [
          ...this.$state.data.eventIndicators.slice(0, targetIndex),
          ...this.$state.data.eventIndicators.slice(targetIndex + 1)
        ];
      }
    } else {
      target.indicators = [
        ...target.indicators.slice(0, index),
        ...target.indicators.slice(index + 1)
      ];
    }

    this.fetchData();
    this.update();
  } // $remove_indicator_value

  $add_indicator_value({ event, newItem }) {
    let target = _.find(this.$state.data.eventIndicators, p => p.event === event);
    if (!target) {
      return;
    }

    let subValue = null;
    let getDisplayText = null;
    if (newItem.object.id !== undefined) {
      let defaultExtension = defaultOptionExtensions[0];
      getDisplayText = defaultExtension.getDisplayText;
      subValue = defaultExtension.value;
    }

    target.indicators.push({
      value: newItem.value,
      getDisplayText,
      subValue
    });

    this.fetchData();
    this.update();
  } // $add_indicator_value

  $change_indicator_value({ event, index, item }) {
    let target = _.find(this.$state.data.eventIndicators, p => p.event === event);
    if (!target) {
      return;
    }

    target.indicators[index] = {
      value: item.value,
      getDisplayText: item.extension ? item.extension.getDisplayText : null,
      subValue: item.extension ? item.extension.value : null
    };

    this.fetchData();
    this.update();
  } // $change_indicator_value

  $change_indicator({ index, event }) {
    let target = this.$state.data.eventIndicators[index];
    if (!target) {
      return;
    }

    this.$state.data.eventIndicators[index] = {
      event,
      indicators: defaultEventIndicator.get('indicators').toJS()
    };

    this.fetchEventIndicatorProperties(() => {
      this.fetchData();
      this.update(0)
    });
    this.update();
  } // $change_indicator

  $add_event() {
    if (!this.$state.events.data) {
      return;
    }

    let event = _.find(this.$state.events.data,
      p => !_.some(this.$state.data.eventIndicators, r => r.event === p.value));
    if (!event) {
      return;
    }

    this.$state.data.eventIndicators.push({
      event: event.value,
      indicators: [
        {
          value: defaultEventIndicatorItems.getIn(['options', 0, 'value']),
          getDisplayText: null,
          subValue: null
        }
      ]
    });

    this.fetchEventIndicatorProperties(() => {
      this.fetchData();
      this.update();
    });
    this.update();
  } // $add_event

  $remove_event({ index }) {
    if (this.$state.data.eventIndicators.length === 1) {
      Info('最后一项事件指标不能删除');
      return;
    }

    this.removeEventTrigger(index);

    this.$state.data.eventIndicators = [
      ...this.$state.data.eventIndicators.slice(0, index),
      ...this.$state.data.eventIndicators.slice(index + 1)
    ];

    this.fetchData();
    this.update();
  } // $remove_event

  removeEventTrigger(index) {
    let event = (this.$state.data.eventIndicators[index] || {}).event;
    if (!event) {
      return;
    }

    if (!this.$state.events.data) {
      return;
    }

    let target = _.find(this.$state.events.data, p => p.value === event);
    if (!target) {
      return;
    }

    target.disabled = false;
  } // removeEventTrigger

  $change_dimension({ index, item }) {
    let target = this.$state.data.viewDimensions[index];
    if (!target) {
      return;
    }

    let viewDimension = {
      property: item.value,
      object: item.object
    };

    if (item.object && item.object.data_type === 'number') {
      viewDimension.settings = defaultViewDimensionNumberSettins.toJS();
    }

    this.$state.data.viewDimensions[index] = viewDimension;

    this.fetchData();
    this.update();
  } // $change_dimension

  $complete_number_setting({ index, settings }) {
    let target = this.$state.data.viewDimensions[index];
    if (!target) {
      return;
    }

    let currentSettings = fromJS(target.settings);
    let newSettings = fromJS(settings);
    if (!is(currentSettings, newSettings)) {
      target.settings = settings;

      this.fetchData();
      this.update();
    } else {
      this.update(0);
    }
  } // $complete_number_setting

  $add_dimension({ newItem }) {
    let viewDimension = {
      property: newItem.value,
      object: newItem.object
    };

    if (newItem.object && newItem.object.data_type === 'number') {
      viewDimension.settings = defaultViewDimensionNumberSettins.toJS();
    }

    this.$state.data.viewDimensions.push(viewDimension);

    this.fetchData();
    this.update();
  } // $add_dimension

  $remove_dimension({ index }) {
    if (this.$state.data.viewDimensions.length === 1) {
      Info('最后一项事件维度不能删除');
      return;
    }

    this.$state.data.viewDimensions = [
      ...this.$state.data.viewDimensions.slice(0, index),
      ...this.$state.data.viewDimensions.slice(index + 1)
    ];

    this.fetchData();
    this.update();
  } // $remove_dimension

  $init_filter() {
    let defaultFilter = this.$state.filterConditionOptions.defaultFilter.toJS();
    this.$state.data.filterConditions = {
      relation: 'and',
      filters: [defaultFilter]
    };

    this.fetchSuggests(defaultFilter.object, true);
    this.update();
  } // $init_filter

  $add_filter() {
    this.$state.data.filterConditions.filters.push(
      this.$state.filterConditionOptions.defaultFilter.toJS());

    this.update();
  } // $add_filter

  $change_filter_field({ index, item }) {
    let target = this.$state.data.filterConditions.filters[index];
    if (!target) {
      return;
    }

    this.$state.data.filterConditions.filters[index] = {
      field: item.value,
      object: item.object,
      suggests: () => this.$state.propertySuggests[item.object.id],
      func: _.find(Operators[item.object.data_type], p => p.isDefault).value,
      filter: null
    };

    this.fetchSuggests(item.object, false, 0);
    this.update();
  } // $change_filter_field

  $change_filter_func({ index, item }) {
    let target = this.$state.data.filterConditions.filters[index];
    if (!target) {
      return;
    }

    _.extend(target, {
      func: item.value,
      filter: item.valueType ? null : nullFilterValue
    });

    if (item.valueType) {
      this.update(0);
    } else {
      this.fetchData();
      this.update();
    }
  } // $change_filter_func

  $change_filter_relation({ relation }) {
    this.$state.data.filterConditions.relation = relation;

    this.fetchData();
    this.update();
  } // $change_filter_relation

  $change_filter_filter({ index, filter }) {
    let target = this.$state.data.filterConditions.filters[index];
    if (!target) {
      return;
    }

    let currentFilter = fromJS(target.filter);
    let newFilter = fromJS(filter);
    if (!is(currentFilter, filter)) {
      target.filter = filter;

      let funcInstance = _.find(Operators[target.object.data_type], p => p.value === target.func);
      if (funcInstance.valueType.indexOf('select') !== -1) {
        this.fetchData();
        this.update();
      } else {
        this.update(0);
      }
    }
  } // $change_filter_filter

  $remove_filter({ index }) {
    if (this.$state.data.filterConditions.filters.length === 1) {
      this.$state.data.filterConditions = null;
    } else {
      this.$state.data.filterConditions.filters = [
        ...this.$state.data.filterConditions.filters.slice(0, index),
        ...this.$state.data.filterConditions.filters.slice(index + 1)
      ];
    }

    this.fetchData();
    this.update();
  } // $remove_filter

  $complete_filter_value({ index }) {
    this.fetchData();
  } // $complete_filter_value

  $change_chart_type({ newType }) {
    this.$state.chart0Settings.chartType = newType;
    this.$state.chart0Settings.settings = newType === 'column' ? {
      plotOptions: {
        series: {
          borderWidth: 0
        },
        column: {
          stacking: 'normal'
        }
      }
    } : {};

    if (newType === 'pie') {
      this.fetchData(true);
    }
    this.update();
  } // $change_chart_type

  $change_date_type({ dateType }) {
    this.$state.chart0Settings.dateType = dateType;

    this.fetchData();
    this.update(0);
  } // $change_date_type

  $added_favorite({ data: { id, name } }) {
    this.$state.favoriteId = id;
    this.$state.favoriteName = name;

    this.emit();
  } // $added_favorite

  $updated_favorite(payload) {
    if (payload.data.contentType === '/segmentation/' && this.$state.favoriteId === payload.data.id) {
      this.$added_favorite(payload);
    }
  } // $updated_favorite

  $removed_favorite({ data: { id, contentType } }) {
    if (contentType === '/segmentation/' && this.$state.favoriteId === id) {
      this.$state.favoriteId = null;
      this.$state.favoriteName = null;

      this.emit();
    }
  } // removed_favorite

  $change_chart_percent({ value }) {
    this.$state.chart0Settings.percent = value;

    this.emit();
  } // $change_chart_percent

  $refresh_data() {
    this.fetchData(true);
  } // $refresh_data

  $reset_data_source({ item }) {
    if (!item) {
      return;
    }

    let res = JSON.parse(item.data);

    this.reset_data(res, () => {
      this.$state.favoriteId = item.id;
      this.$state.favoriteName = item.name;
      this.$state.date = this.transformDateInfo(item, res);
    });
  } // $reset_data_source

  $set_date({ data: { isCustom, isNatural, item: { alias, value: { spanTime, endTime } } } }) {
    this.$state.date.isCustom = isCustom;
    this.$state.date.isNatural = isNatural;
    this.$state.date.alias = alias;
    this.$state.date.picker = {
      endTime,
      spanTime
    };

    this.$state.showCopmarer = false;
    this.$state.date.comparer = null;

    this.fetchData();
  } // $set_date

  $toggle_comparer() {
    this.$state.showCopmarer = !this.$state.showCopmarer;

    this.$state.date.comparer = !this.$state.showCopmarer ? null : {
      endTime: this.$state.date.picker.endTime - this.$state.date.picker.spanTime - oneDayValueOf,
      spanTime: this.$state.date.picker.spanTime
    };

    this.fetchData();
    this.update();
  } // $toggle_comparer

  $set_date_comparer({ data: { endTime, spanTime } }) {
    this.$state.date.comparer = !this.$state.showCopmarer ? null : {
      endTime,
      spanTime
    };

    this.fetchData();
    this.update();
  } // $set_date_comparer

  $set_selected_column({ title, checked }) {
    let columns = _.filter(this.$state.selectedColumns, p => p !== title);
    this.$state.selectedColumns = checked ? columns.concat(title) : columns;

    this.emit();
  } // $set_selected_column

  $show_data_grid_total({ checked }) {
    this.$state.showDataGridTotal = checked;

    this.ep.once('pie', () => this.dataGridConverter());
    if (checked) {
      this.fetchPieChartData(this.immutableParameter.toJS());
    } else {
      this.ep.emit('pie');
    }
    this.update();
  } // $show_data_grid_total

  $switch_event_table_type({ tableType }) {
    this.$state.showEventTableType = tableType;

    this.dataGridConverter();
  } // $switch_event_table_type

  $jump_to_subpage({ callback }) {
    let condition = queryStore.get();

    condition = callback(condition);
    condition.rollup_date = this.$state.showDataGridTotal + '';
    condition.use_cache = false;
    condition.all_page = false;
    condition.detail = 'true';

    this.viewInstance.props.history.pushState(null, `/users?${ queryStore.params(condition) }`)
  } // $jump_to_subpage

  refresh_state() {
    let query = queryStore.get();
    if (_.size(query)) {
      this.try_reset_state(query);
    } else {
      this.fetchData();
    }
  } // refresh_state

  try_reset_state(newState) {
    this.reset_data(newState, () => {
      this.$state.favoriteId = null;
      this.$state.favoriteName = null;

      let formatter = 'YYYY-MM-DD'
        , begin = moment(newState.from_date, formatter).valueOf()
        , end = moment(newState.to_date, formatter).valueOf()
        , span = end - begin;

      let item = getDayFromFunction(p => p.value.spanTime === span && p.value.endTime === end);
      if (item) {
        this.$state.date = {
          isNatural: item.isNatural,
          isCustom: false,
          alias: item.item.alias,
          picker: {
            spanTime: span,
            endTime: end
          },
          comparer: null
        };
      } else {
        this.$state.date = {
          isNatural: true,
          isCustom: true,
          alias: null,
          picker: {
            spanTime: span,
            endTime: end
          },
          comparer: null
        };
      }

      if (newState.compare_from_date) {
        this.$state.showCopmarer = true;

        let comparerBegin = moment(newState.compare_from_date, formatter).valueOf()
          , comparerEnd = moment(newState.compare_to_date, formatter).valueOf()
          , comparerSpan = comparerEnd - comparerBegin;

        this.$state.date.comparer = {
          spanTime: comparerSpan,
          endTime: comparerEnd
        };
      }
    });
  } // try_reset_state

  reset_data(newState, otherCallback) {
    let unit = this.$state.chart0Settings.dateType;
    let current = {
      favoriteId: this.$state.favoriteId,
      favoriteName: this.$state.favoriteName,
      showCopmarer: this.$state.showCopmarer,
      date: this.$state.date,
      data: this.$state.data
    };

    otherCallback();

    let data = {};
    data.eventIndicators = this.transformEventIndicator(newState.measures);

    let filterConditions = fromJS(this.$state.data.filterConditions)
    data.filterConditions = filterConditions ? filterConditions.toJS() : null;

    data.viewDimensions = fromJS(this.$state.data.viewDimensions).toJS();

    this.$state.data = data;
    this.$state.chart0Settings.dateType = newState.unit;

    this.fetchEventIndicatorProperties(() => {
      try {
        data.viewDimensions = this.transformViewDimension(newState.by_fields || [], newState.bucket_params);
        this.refreshViewDimension();

        data.filterConditions = newState.filter ? this.transformFilterCondition(newState.filter) : null;
        if (data.filterConditions) {
          this.refreshFilterConditions();
        }

        this.$state.data = data;

        this.fetchEventIndicatorProperties(() => {
          this.fetchData();
          this.update();
        });
      } catch (e) {
        Warning(e.message);

        _.extend(this.$state, current);
        this.$state.chart0Settings.dateType = unit;
        this.emit();
      }
    });
  } // reset_data

  transformEventIndicator(measures) {
    let result = _.chain(measures)
      .groupBy('event_name')
      .map((measureArray, key) => {
        let indicators = _.chain(measureArray)
          .map(measure => {
            if (!measure.field) {
              return {
                value: measure.aggregator,
                subValue: null,
                getDisplayText: null
              };
            } else {
              let extension = _.find(defaultOptionExtensions, p => p.value === measure.aggregator);
              return {
                value: measure.field,
                subValue: measure.aggregator,
                getDisplayText: extension.getDisplayText
              };
            }
          })
          .value();

        return {
          event: key,
          indicators
        };
      })
      .value();

    return result;
  } // transformEventIndicator

  transformViewDimension(by_fields, bucket_params = {}) {
    let result = _.chain(by_fields)
      .map(field => {
        let viewDimension = {
          property: field
        };

        let segments = field.split('.');
        let viewDimensionValue = segments[segments.length - 1];
        if (segments.length === 3) {
          let obj = this.$state
                        .viewDimensionOptions
                        .data
                        .getIn([1, 'options'])
                        .find(p => p.get('value').split('.')[2] === viewDimensionValue);

          if (!obj) {
            throw new Error('此查询的相关事件或属性不存在，请更改查询条件');
          }

          viewDimension.object = obj.get('object').toJS();
        } else { // segments.length === 2
          let obj = this.$state
                        .viewDimensionOptions
                        .data
                        .getIn([2, 'options'])
                        .find(p => p.get('value').split('.')[1] === viewDimensionValue);

          if (!obj) {
            throw new Error('此查询的相关事件或属性不存在，请更改查询条件');
          }

          viewDimension.object = obj.get('object').toJS();
        }

        return viewDimension;
      })
      .each(viewDimension => {
        let values = bucket_params[viewDimension.property];
        if (values) {
          if (values.length === 1 && values[0] === null) {
            viewDimension.settings = {
              segmentType: 0,
              values: [null]
            };
          } else {
            viewDimension.settings = {
              segmentType: 2,
              values
            };
          }
        }
      })
      .value();

    return result;
  } // transformViewDimension

  transformFilterCondition(filter = {}) {
    let result = {
      relation: filter.relation || 'and'
    };

    if (!filter.conditions) {
      return null;
    }

    let conditions = _.map(filter.conditions, condition => {
      let item = {
        field: condition.field,
        func: condition.function,
        filter: condition.params
      };

      let segments = condition.field.split('.');
      let filterConditionValue = segments[segments.length - 1];
      if (segments.length === 3) {
        let obj = this.$state
                      .filterConditionOptions
                      .data
                      .getIn([0, 'options'])
                      .find(p => p.get('value').split('.')[2] === filterConditionValue)
                      .get('object')
                      .toJS();
        item.object = obj;
      } else { // segments.length === 2
        let obj = this.$state
                      .filterConditionOptions
                      .data
                      .getIn([1, 'options'])
                      .find(p => p.get('value').split('.')[1] === filterConditionValue)
                      .get('object')
                      .toJS();
        item.object = obj;
      }

      if (_.find(Operators[item.object.data_type], p => p.value === item.func).valueType.indexOf('select') !== -1) {
        let obj = item.object;
        item.suggests = () => {
          let suggests = this.$state.propertySuggests[obj.id];
          if (!suggests) {
            suggests = { loading: true };

            this.fetchSuggests(obj, false, 0);
          }

          return suggests;
        };
      }

      return item;
    });
    result.filters = conditions;

    return result;
  } // transformFilterCondition

  transformDateInfo(item, res){
    if (!item.time) {
      this.$state.showCopmarer = false;

      return {
        isNatural: false,
        isCustom: false,
        alias: '7 day',
        picker: {
          to_date: moment().startOf('day').valueOf(),
          spanTime: moment.duration(7, 'days').valueOf()
        },
        comparer: null
      };
    }

    let dateInfo = getDayFromAlias(item.time);
    let result = {
      isNatural: dateInfo.isNatural,
      isCustom: false,
      alias: item.time,
      picker: dateInfo.item.value
    };

    this.$state.showCopmarer = !!res.compare_to_date;
    if (res.compare_to_date) {
      result.comparer = _.extend({}, dateInfo.item.value, {
        endTime: dateInfo.item.value.endTime - dateInfo.item.value.spanTime - oneDayValueOf
      });
    }

    return result;
  } // transformDateInfo

  fetchEvents() {
    this.eventsApi
      .fetch()
      .progress(() => {
        this.$state.events = {
          loading: true
        };

        this.emit();
      })
      .fail(error => this.$state.events = {
        error: error.responseText
      })
      .done(data => this.$state.events = {
        data: [
          anythingEvent.toJS(),
          ..._.chain(data)
            .filter(p => p.visible)
            .sortBy(p => p.cname)
            .map(p => ({
              label: p.cname,
              value: p.name,
              object: p
            }))
            .value()
        ]
      })
      .always(() => {
        this.update();
        this.ep.emit('events');
      });
  } // fetchEvents

  fetchEventIndicatorProperties(callback) {
    if (!this.$state.data.eventIndicators.length) {
      this.$state.data.eventIndicators.push(defaultEventIndicator.toJS());
    }

    let resettleIntersctProperties = () => {
      let keys = _.map(this.$state.data.eventIndicators, p => p.event);
      let intersects = _.chain(this.$state.properties)
                        .filter((value, key) => value.data && _.contains(keys, key))
                        .map(p => p.data.get('intersection').toJS());

      let events = intersects.map(p => p.event)
                             .reduce((x, y) => _.intersect(x, y, (a, b) => a.id === b.id))
                             .value();
      let eventOptions = _.chain(events)
                          .filter(p => p.name.indexOf('$') === 0)
                          .map(p => ({
                            label: p.cname,
                            value: `event.${p.event_name}.${p.name}`,
                            object: p
                          }))
                          .concat(
                            _.chain(events)
                             .filter(p => p.name.indexOf('$') !== 0)
                             .map((p, i) => ({
                               label: p.cname,
                               value: `event.${p.event_name}.${p.name}`,
                               object: p,
                               className: i === 0 ? 'custom-event' : null
                             }))
                             .value()
                          )
                          .value();

      let users = intersects.map(p => p.user)
                            .reduce((x, y) => _.intersect(x, y, (a, b) => a.id === b.id))
                            .value();
      let userOptions = _.chain(users)
                         .filter(p => p.name.indexOf('$') === 0)
                         .map(p => ({
                           label: p.cname,
                           value: `user.${p.name}`,
                           object: p
                         }))
                         .concat(
                           _.chain(users)
                            .filter(p => p.name.indexOf('$') !== 0)
                            .map((p, i) => ({
                              label: p.cname,
                              value: `user.${p.name}`,
                              object: p,
                              className: i === 0 ? 'custom-event' : null
                            }))
                            .value()
                         )
                         .value();

      let options = [
        {
          group: '事件属性',
          options: eventOptions
        },
        {
          group: '用户属性',
          options: userOptions
        }
      ];

      // 处理名称相同的选项
      _.chain(eventOptions)
        .union(userOptions)
        .groupBy('label')
        .each((v, k) => {
          if (v.length > 1) {
            for (let i = 0; i < v.length; i++) {
              let item = v[i];

              if (_.indexOf(eventOptions, item) !== -1) {
                item.displayText = `${item.label} - 事件属性`;
              } else {
                item.displayText = `${item.label} - 用户属性`;
              }
            }
          }
        });

      let immutableOptions = fromJS(options);
      if (!is(this.$state.intersectProperties, immutableOptions)) {
        this.$state.intersectProperties = immutableOptions;

        this.refreshViewDimension(options);
        this.refreshFilterConditions(options);
      }

      callback();
    };

    let ep = EventProxy.create();
    // 获取需要加载的事件属性数据源（多项事件）
    _.chain(this.$state.data.eventIndicators)
      .map(p => p.event)
      .filter(p => {
        let props = this.$state.properties[p];
        return !props || !props.data || props.error ||
          (props.loading !== undefined && props.loading !== true)
      })
      .tap(p => ep.all(p, resettleIntersctProperties))
      .each((p, index) => {
        this.$state.properties[p] = {
          loading: true
        };
        this.fetchProperties(p).always(() => ep.emit(p));
      });

    if (!_.keys(ep._callbacks).length) {
      resettleIntersctProperties();
    }
  } // fetchEventIndicatorProperties

  refreshViewDimension(options = this.$state.intersectProperties.toJS()) {
    let iOptions = fromJS([
      totalDimension,
      {
        group: options[0].group,
        options: _.filter(options[0].options, p => p.object.is_dimension)
      },
      {
        group: options[1].group,
        options: _.filter(options[1].options, p => p.object.is_dimension)
      }
    ]);

    if (!is(this.$state.viewDimensionOptions, iOptions)) {
      this.$state.viewDimensionOptions = {
        data: iOptions
      };

      if (this.$state.data.viewDimensions.length) {
        let flattenedOptions = _.chain(options)
                                .map(p => p.options)
                                .flatten(true);

        let nonExists = _.chain(this.$state.data.viewDimensions)
                         .filter(p => p.property !== '-1' && !flattenedOptions.some(r => r.object.id === p.object.id).value());

        let exists = _.chain(this.$state.data.viewDimensions)
                      .filter(p => p.property === '-1' || !nonExists.some(r => r.object.id === p.object.id).value())
                      .each(p => p.property !== '-1'
                              ? _.tap(p, r => {
                                r.object = flattenedOptions.find(s => s.object.id === p.object.id).value().object;
                                r.property = r.property.split('.').length === 3
                                  ? `event.${ r.object.event_name }.${ r.object.name }`
                                  : `user.${ r.object.name }`;
                                })
                              : p)
                      .value();

        this.$state.data.viewDimensions = exists;
      }
    }

    if (!this.$state.data.viewDimensions.length) {
      this.$state.data.viewDimensions.push(defaultViewDimension.toJS())
    }
  } // refreshViewDimension

  refreshFilterConditions(options = this.$state.intersectProperties.toJS()) {
    let defaultField = options[0].options[0];
    let immutableOptions = fromJS(options);

    if(!is(this.$state.filterConditionOptions.data, immutableOptions)) {
      let defaultFilter = {
        field: defaultField.value,
        object: defaultField.object,
        suggests: () => this.$state.propertySuggests[defaultField.object.id],
        func: _.find(Operators[defaultField.object.data_type], p => p.isDefault).value,
        filter: []
      };

      this.$state.filterConditionOptions = {
        defaultFilter: fromJS(defaultFilter),
        data: fromJS(options)
      };

      if (this.$state.data.filterConditions && this.$state.data.filterConditions.filters.length) {
        let flattenedOptions = _.chain(options)
                                .map(p => p.options)
                                .flatten(true);

        let nonExists = _.chain(this.$state.data.filterConditions.filters)
                        .filter(p => !flattenedOptions.some(r => r.object.id === p.object.id).value());

        let exists = _.chain(this.$state.data.filterConditions.filters)
                      .filter(p => p.property === '-1' || !nonExists.some(r => r.object.id === p.object.id).value())
                      .each(p => p.property !== '-1'
                              ? _.tap(p, r => {
                                r.object = flattenedOptions.find(s => s.object.id === p.object.id).value().object;
                                r.field = r.field.split('.').length === 3
                                  ? `event.${ r.object.event_name }.${ r.object.name }`
                                  : `user.${ r.object.name }`
                                })
                              : p)
                      .value();

        this.$state.data.filterConditions.filters = exists;
        if (!exists.length) {
          this.$state.data.filterConditions = null;
        }
      }
    }

    let hasFilter = this.$state.data.filterConditions !== null;
    if (!hasFilter) {
      this.ep.emit('suggests', 'waitFireSuggests: false');
    }

    this.fetchSuggests(defaultField.object, hasFilter);
  } // refreshFilterConditions

  fetchSuggests(obj, fireSuggests = true, wait = 100) {
    let fire = value => {
      if (fireSuggests) {
        this.ep.emit('suggests', value);
      }
    };

    if (!(obj.data_type === 'string' || obj.data_type === 'list')) {
      fire('!(obj.data_type === string || obj.data_type === list)');
      return;
    }

    let target = this.$state.propertySuggests[obj.id];
    if (target && (target.data || target.loading)) {
      fire('target && target.data');
      return;
    }

    this.suggestApi({ url: `sd/${ BlueWare.appId }/api/property/${obj.id}/values` })
        .progress(() => this.$state.propertySuggests[obj.id] = { loading: true })
        .fail(error => this.$state.propertySuggests[obj.id] = { error })
        .done(data => this.$state.propertySuggests[obj.id] = {
          data: data.map(item => ({
            label: item,
            value: item
          }))
        })
        .always(() => {
          fire();
          this.update(wait);
        });
  } // fetchSuggests

  fetchProperties(eventValue) {
    return request2({ url: `sd/${ BlueWare.appId }/api/event/properties?events=${eventValue}&method=mixed` })
      .fail(error => this.$state.properties[eventValue] = { error: error.responseText })
      .done(data => this.$state.properties[eventValue] = {
        data: fromJS(data)
      });
  } // fetchProperties

  pieChartDataConvert(data, name) {
    let sum = _.reduce(data.rows, (x, y) => x + y.values[0][0], 0);
    let series = _.chain(data.rows)
                  .map(p => [data.series[0], p.values[0][0]])
                  .map((point, index) => {
                    let chartName = this.getChartRowName(data, index);
                    let timer = moment(point[0]).valueOf();
                    let percent = point[1] / sum;
                    let list = [{
                      x: timer,
                      y: percent,
                      customData: {
                        y: {
                          label: chartName,
                          yValue: point[1],
                          fmtVal: format.rate(percent)
                        }
                      }
                    }];

                    return {
                      id: index,
                      name: chartName,
                      data: list
                    };
                  })
                  .value();

    return {
      yAxis: {
        labels: {
          formatter: function () {
            return format.rate(this.value);
          }
        },
        maxPadding: 0
      },
      series
    };
  } // pieChartDataConvert

  chartDataConvert(data, suffix = '', dashStyle = 'Solid', indexBase = 0) {
    let series = _.chain(data.rows)
                  .map(p => _.zip(_.filter(data.series, (r, i) => i < p.values.length), p.values))
                  .map((points, index) => {
                    let chartName = this.getChartRowName(data, index);
                    let list = _.chain(points)
                                .map((p, i) => ({
                                  x: moment(p[0]).valueOf(),
                                  y: p[1][0],
                                  suffix,
                                  labelSerie: moment(data.labelSeries[i]).valueOf(),
                                  customData: {
                                    y: {
                                      label: chartName,
                                      fmtVal: p[1][0]
                                    }
                                  }
                                }));

                    list.reduce((x, y) => {
                      x.customData.startTime = x.x;
                      x.customData.endTime = y.x;

                      return y;
                    });

                    let first = list.first().value();
                    let timeRange = first.customData.endTime - first.customData.startTime;

                    let last = list.last().value();
                    last.customData.startTime = last.x;
                    last.customData.endTime = last.customData.startTime + timeRange;

                    let result = {
                      id: indexBase + index,
                      name: chartName + suffix,
                      color: defaultChartColors[index % 10],
                      dashStyle: dashStyle,
                      data: list.value(),
                      stack: dashStyle
                    };

                    if (dashStyle !== 'Solid') {
                      result.color = defaultComparedChartColors[index % 10];
                    }

                    return result;
                  })
                  .value();

    _.chain(series).map(p => p.data).each(p => {
      _.each(p, (r, i) => r.customData.y = _.extend(r.customData.y, {
        fmtVal: `${ r.customData.y.fmtVal } ${ this.$state.chart0Settings.unit || '' }`
      }));
    });

    return {
      yAxis: {
        minTickInterval: 1,
        labels: {
          formatter: function () {
            return format.integer(this.value);
          }
        }
      },
      series
    };
  } // chartDataConvert

  dataGridConverter() {
    // 重置数据表格设置信息状态
    _.extend(this.$state, {
      showSelectedColumns: false,
      selectedColumns: [],
      dataGridColumns: []
    });

    let grid = this.$state.fetchedData.grid;
    if (!_.size(grid.data)) {
      this.$state.dataGrid = { data: {} };
      this.emit();
      return;
    }

    let dataList = _.chain(grid.data.series);
    if (grid.data1) {
      dataList = dataList.concat(grid.data1.series);
    }
    let multiYear = dataList.map(p => moment(p))
                            .groupBy(p => p.year())
                            .size()
                            .value() !== 1;

    let data = null;
    let sum = null;
    if (this.$state.showEventTableType === 'current') {
      data = this.$state.fetchedData.grid.data;
      sum = this.$state.fetchedData.sum.data;
    } else if (this.$state.showEventTableType === 'comparer') {
      data = this.$state.fetchedData.grid.data1;
      sum = this.$state.fetchedData.sum.data1;
    }

    let columns = this.getDataGridBasicColumns();
    let dynamicColumns = this.dataGridDynamicColumnGenerator();
    let rows = this.$state.showEventTableType !== 'meta'
      ? this.dataGridRowGenerator(data, multiYear)
      : this.dataGridRowMetaGenerator(this.$state.fetchedData.grid.data, this.$state.fetchedData.grid.data1);

    this.$state.dataGridColumns = columns.concat(dynamicColumns);
    this.$state.dataGrid = {
      data: {
        rows,
        sum: !this.$state.showDataGridTotal ? null
               : (
                 this.$state.showEventTableType !== 'meta'
                   ? this.dataGridRowGenerator(sum, multiYear)
                   : this.dataGridRowMetaGenerator(this.$state.fetchedData.sum.data, this.$state.fetchedData.sum.data1)
               )
      }
    };

    let dimensionCount = _.size(this.$state.basicData.viewDimensionDic);
    if (this.$state.basicData.eventIndicatorNames.length === 1 && dimensionCount > 0 && dimensionCount < 3) {
      this.$state.selectedColumns = rows.length > 0 && rows[0].value.length > 3
        ? _.chain(rows[0].value).filter((p, i) => i < 3).map(p => p[1]).value()
        : [];
      this.$state.showSelectedColumns = this.$state.selectedColumns.length > 0;
    }

    this.emit();
  } // dataGridConverter

  dataGridRowMetaGenerator(currentList, comparerList, isAsc = false) {
    if (!currentList || !comparerList) {
      return [];
    }

    let multiYear = _.chain(currentList.series)
                     .concat(comparerList.series)
                     .map(p => moment(p))
                     .groupBy(p => p.year())
                     .size()
                     .value() !== 1;
    let pattern = this.getDatePattern(multiYear);

    let dates = [];
    let currentNewSeries = [];
    let comparedNewSeries = [];
    if (currentList.series.length === comparerList.series.length) {
      dates = _.chain(currentList.series)
               .zip(comparerList.series)
               .map(p => {
                 let date0 = moment(p[0]).valueOf();
                 let date1 = moment(p[1]).valueOf();
                 if (date0 < date1) {
                   [date0, date1] = [date1, date0];
                 }
                 return `${ pattern(date0) } / ${ pattern(date1) }`;
               })
               .value();
    } else {
      let series = currentList.series.length < comparerList.series.length ? currentList.series : comparerList.series;
      let normalSeries = currentList.series.length > comparerList.series.length ? currentList.series : comparerList.series;

      let groupedCurrentSeries = _.chain(currentList.series)
                                  .map(p => moment(p).valueOf())
                                  .groupBy(p => moment(p).startOf('day').valueOf())
                                  .map((v, k) => ({
                                    values: v,
                                    key: +k
                                  }))
                                  .value();
      let groupedComparedSeries = _.chain(comparerList.series)
                                   .map(p => moment(p).valueOf())
                                   .groupBy(p => moment(p.valueOf()).startOf('day').valueOf())
                                   .map((v, k) => ({
                                     values: v,
                                     key: +k
                                   }))
                                   .value();

      _.each(groupedCurrentSeries, (current, index) => {
        let compared = groupedComparedSeries[index];

        if (current.values.length === compared.values.length) {
          return;
        }

        let leftZero = current.key;
        let leftTimes = _.map(current.values, p => p - leftZero);
        let rightZero = compared.key;
        let rightTimes = _.map(compared.values, p => p - rightZero);

        if (current.values.length > compared.values.length) {
          let rightSeries = _.chain(leftTimes)
                             .reject(p => _.contains(rightTimes, p))
                             .map(p => moment(rightZero + p).format('YYYY-MM-DD HH:mm:ss'))
                             .value();
          comparedNewSeries = comparedNewSeries.concat(rightSeries);
        } else if (current.values.length < compared.values.length) {}
          let leftSeries = _.chain(rightTimes)
                            .reject(p => _.contains(leftTimes, p))
                            .map(p => moment(leftZero + p).format('YYYY-MM-DD HH:mm:ss'))
                            .value();
          currentNewSeries = currentNewSeries.concat(leftSeries);
      });

      let left = _.chain(currentList.series)
                  .concat(currentNewSeries)
                  .sortBy(p => moment(p).valueOf())
                  .value();
      let right = _.chain(comparerList.series)
                   .concat(comparedNewSeries)
                   .sortBy(p => moment(p).valueOf())
                   .value();
      dates = _.chain(left)
               .zip(right)
               .map(p => `${ moment(p[0]).format(pattern) } / ${ moment(p[1]).format(pattern) }`)
               .value();
    }

    let leftRows = fromJS(currentList.rows).toJS();
    if (currentNewSeries.length) {
      let len = leftRows[0].values[0].length;
      let data = [];
      for (let i = 0; i < len; i++) {
        data.push(null);
      }

      _.each(leftRows, p => {
        for (var i = 0; i < currentNewSeries.length; i++) {
          p.values.push(data);
        }
      });
    }

    let rightRows = fromJS(comparerList.rows).toJS();
    if (comparedNewSeries.length) {
      let len = rightRows[0].values[0].length;
      let data = [];
      for (let i = 0; i < len; i++) {
        data.push(null);
      }

      _.each(rightRows, p => {
        for (var i = 0; i < currentNewSeries.length; i++) {
          p.values.push(data);
        }
      });
    }

    let rows = _.chain(leftRows)
                .zip(rightRows)
                .map(item => {
                  let dimensions = _.map(currentList.by_fields, (field, fieldIndex) => {
                                      let selectedViewDimension = this.getViewDimensionItem(field);
                                      let dataType = selectedViewDimension.object.data_type;
                                      return this.convertDimensionDesc(dataType, item[0].by_values[fieldIndex]);
                                    })
                                    .join(', ');
                  return _.chain(item[0].values)
                          .zip(item[1].values)
                          .map((cell, index) => {
                            let values = [dates[index]];
                            if (dimensions) {
                              values.push(dimensions);
                            }

                            for (let i = 0; i < cell[0].length; i++) {
                              if (_.chain(cell).flatten().some(p => p === null).value() || cell[0][i] === 0) {
                                values.push({ value: '--' });
                              } else {
                                let currentValue = (cell[1][i] - cell[0][i]) / cell[0][i];
                                values.push({
                                  percentValue: currentValue,
                                  percent: numeral(currentValue).format('+0[.]0%')
                                });
                              }
                            }

                            return values;
                          })
                          .value();
                })
                .flatten(true)
                .groupBy(p => p[0])
                .map((value, key) => ({
                  value,
                  key
                }))
                .sortBy(p => p.key);

    if (!isAsc) {
      rows = rows.reverse();
    }

    return rows.value();
  } // dataGridRowMetaGenerator

  getDatePattern(multiYear = true) {
    let { dateType } = this.$state.chart0Settings;
    if (dateType === 'hour') {
      return value => moment(value).format(multiYear ? 'YYYY-MM-DD HH:mm' : 'MM-DD HH:mm');
    } else if (dateType === 'day') {
      return value => moment(value).format(multiYear ? 'YYYY-MM-DD' : 'MM-DD');
    } else if (dateType === 'week') {
      return value => {
        let formatter = `${ multiYear ? 'YYYY-MM-DD' : 'MM-DD' }`;

        return `${ moment(value).format(formatter) } ~ ${ moment(value).add(6, 'days').format(formatter) }`;
      }
    } else if (dateType === 'month') {
      return value => moment(value).format(`${ multiYear ? 'YYYY-MM' : 'MM' } 月`);
    } else {
      return value => moment(value).format('YYYY-MM-DD');
    }
  } // getDatePattern

  dataGridRowGenerator(data, multiYear = true, isAsc = false) {
    if (!data) {
      return [];
    }

    let pattern = this.getDatePattern(multiYear);
    let dates = _.map(data.series, p => pattern(p));

    let rows = _.chain(data.rows)
                .map(row => {
                  let by_values = [];
                  let dimensions = _.map(data.by_fields, (field, fieldIndex) => {
                                      let selectedViewDimension = this.getViewDimensionItem(field)
                                        , dataType = selectedViewDimension.object.data_type
                                      	, originalByValues = row.by_values[fieldIndex];

                                      by_values.push(originalByValues)
                                      return this.convertDimensionDesc(dataType, originalByValues);
                                    })
                                    .join(', ');

                  return _.map(row.values, (cells, index) => {
                    let values = [dates[index]];
                    if (dimensions) {
                      values.push(dimensions);
                    }

                    return values.concat(_.map(cells, v => ({
                      value: v,
                      by_values,
                      date: data.series[index]
                    })));
                  });
                })
                .flatten(true)
                .groupBy(p => p[0])
                .map((value, key) => ({
                  value,
                  key
                }))
                .sortBy(p => p.key);

    let exists = data.by_fields.length > 0;
    rows.reduce((x, y) => {
      _.each(_.zip(x.value, y.value), v => {
        let zipped = _.zip(_.filter(v[0], (p, i) => i > (exists ? 1 : 0)), _.filter(v[1], (p, i) => i > (exists ? 1 : 0)));
        _.each(zipped, item => {
          if (item[0] || item[0].value === null || item[1] || item[1].value === null) {
            return;
          }

          if (item[0].value !== 0) {
            item[1].percentValue = (item[1].value - item[0].value) / item[0].value;
            item[1].percent = numeral(item[1].percentValue).format('+0[.]0%');
          }
        });
      })

      return y;
    });

    if (!isAsc) {
      rows = rows.reverse();
    }

    return rows.value();
  } // dataGridRowGenerator

  getDataGridBasicColumns() {
    let self = this;
    let columns = [{
      key: 'date',
      title: '日期',
      className: 'text-align-center',
      sort: function (column, index) {
        if (!self.$state.dataGrid.data) {
          return;
        }

        this.state.dateSortOrientation *= -1;
        self.$state.dataGrid.data.rows = self.$state.dataGrid.data.rows.reverse();
        self.emit();
      }
    }];

    if (_.keys(this.$state.basicData.viewDimensionDic).length) {
      let dimension = {
        key: 'dimension',
        title: _.values(this.$state.basicData.viewDimensionDic).join(', '),
        sort: function (column, index) {
          if (!self.$state.dataGrid.data) {
            return;
          }

          // 设置列的排序
          if (column.key === this.state.currentSort) {
            this.state.currentSortOrientation *= -1;
          } else {
            this.state.currentSort = column.key;
            this.state.currentSortOrientation = -1;
          }

          if (!self.$state.dataGrid.data.rows.length) {
            return;
          }

          if (self.$state.dataGrid.data.rows[0].value.length > 1) {
            self.$state.dataGrid.data.rows = _.each(self.$state.dataGrid.data.rows, item => {
              item.value = _.sortBy(item.value, p => {
                let parsed = parseFloat(p[1]);
                if (isNaN(parsed)) {
                  return p[1];
                } else {
                  return parsed;
                }
              });

              if (this.state.currentSortOrientation === -1) {
                item.value = item.value.reverse();
              }
            });
          } else {
            // 待排序的所有项都相同，不用排序。
          }
          self.emit();
        }
      };
      columns.push(dimension);
    }

    return columns;
  } // getDataGridBasicColumns

  dataGridDynamicColumnGenerator() {
    let self = this;
    let columns = _.map(this.$state.basicData.eventIndicatorNames, item => {
      let indicator = {
        key: item.name,
        title: item.text,
        sort: function (column, index) {
          if (!self.$state.dataGrid.data) {
            return;
          }

          // 设置列的排序
          if (column.key === this.state.currentSort) {
            this.state.currentSortOrientation *= -1;
          } else {
            this.state.currentSort = column.key;
            this.state.currentSortOrientation = -1;
          }

          if (!self.$state.dataGrid.data.rows.length) {
            return;
          }

          if (self.$state.dataGrid.data.rows[0].value.length > 1) {
            self.$state.dataGrid.data.rows = _.each(self.$state.dataGrid.data.rows, item => {
              item.value = _.sortBy(item.value, p => {
                let item = p[index];
                return item.value !== undefined ? item.value : (item.percent || item);
              });

              if (this.state.currentSortOrientation === -1) {
                item.value = item.value.reverse();
              }
            });
          } else {
            let { rows } = self.$state.dataGrid.data;
            rows = _.sortBy(rows, p => {
              let item = p.value[0].slice(1)[index];
              return item.value !== undefined ? item.value : item.percentValue;
            });

            if (this.state.currentSortOrientation === -1) {
              rows = rows.reverse();
            }

            self.$state.dataGrid.data.rows = rows;
          }
          self.emit();
        }
      };
      return indicator;
    });

    return columns;
  } // dataGridDynamicColumnGenerator

  getChartRowName(obj, rowIndex) {
    if (obj.by_fields.length === 0) {
      return this.$state.chart0Settings.char0Name;
    }

    return _.map(obj.by_fields, (field, index) => {
      let nullName = '未知';
      if (!field) {
        return nullName;
      }

      let selectedViewDimension = this.getViewDimensionItem(field);
      let dataType = selectedViewDimension.object.data_type;
      let byValue = (obj.rows[rowIndex].by_values[index] || '').toString()

      return this.convertDimensionDesc(dataType, byValue);
    })
    .join(', ');
  }

  getViewDimensionItem(value) {
    if (value.split('.').length === 2) {
      return _.find(this.$state.intersectProperties.getIn([1, 'options'], []).toJS(), p => p.value === value);
    } else {
      return _.find(this.$state.intersectProperties.getIn([0, 'options'], []).toJS(), p => p.value === value);
    }
  } // getViewDimensionItem

  convertDimensionDesc(dataType, byValue) {
    let nullName = '未知';
    if (byValue === null) {
      return nullName;
    }

    if (byValue === '未知') {
      return '"未知"';
    }

    byValue = byValue.toString();
    if (dataType === 'number') {
      let pair = byValue.split('~');
      if (pair[0] === '-INF') {
        return pair[1] + ' 以下';
      }
      if (pair[1] === 'INF') {
        return pair[0] + ' 以上';
      }
      return byValue || nullName;
    } else if (dataType === 'string') {
      return byValue || nullName;
    } else if (dataType === 'list') {
      return byValue.join(',');
    } else if (dataType === 'bool') {
      return byValue === '1' ? '是' : '否';
    }
  } // convertDimensionDesc

  refreshChartName() {
    let unit = null;
    let data = _.chain(this.$state.data.eventIndicators)
                .map(p => ({
                  eventIndicator: p,
                  object: _.find(this.$state.events.data, r => r.value === p.event)
                }))
                .map(p => {
                  let eventName = p.object.label;

                  let defaultIndicatorOptions = _.chain(defaultEventIndicatorItems.get('options').toJS());
                  let defaultIndicators = _.chain(p.eventIndicator.indicators)
                                           .filter(r => !r.subValue)
                                           .map(r => defaultIndicatorOptions.find(s => s.value === r.value).value())
                                           .filter(r => r)
                                           .map(r => ({
                                             text: r.label,
                                             unit: r.object.unit
                                           }))
                                           .value();

                  let properties = _.chain(this.$state.properties[p.object.value].data.getIn(['original', p.object.value, 'event']).toJS());
                  let numberIndicators = _.chain(p.eventIndicator.indicators)
                                          .filter(r => r.subValue)
                                          .map(r => {
                                            let propertyNames = r.value.split('.');
                                            let propertyName = propertyNames[propertyNames.length - 1];

                                            return {
                                              indicator: r,
                                              option: properties.find(s => s.name === propertyName).value()
                                            };
                                          })
                                          .filter(r => r.option)
                                          .map(r => ({
                                            text: r.indicator.getDisplayText(r.option.cname),
                                            unit: r.option.unit
                                          }))
                                          .value();

                  let indicators = defaultIndicators.concat(numberIndicators);
                  return {
                    text: `${ eventName }的${ _.pluck(indicators, 'text').join('，') }`,
                    units: _.pluck(indicators, 'unit')
                  };
                })
                .value();;

    this.$state.chart0Settings.char0Name = _.pluck(data, 'text').join('，');
    let units = _.chain(data).pluck('units').flatten(true).filter(p => p || p === '').value();
    if (units.length === 1 && _.every(units, p => p)) {
      this.$state.chart0Settings.unit = units[0];
    } else {
      this.$state.chart0Settings.unit = null;
    }
  } // refreshChartName

  getEventIndicator() {
    let measures = _.chain(this.$state.data.eventIndicators)
                    .map(p => _.map(p.indicators, r =>
                      r.subValue ? {
                        event_name: p.event,
                        aggregator: r.subValue,
                        field: r.value
                      } : {
                        event_name: p.event,
                        aggregator: r.value
                      }
                    ))
                    .flatten(true)
                    .value();

    let names = [];
    if (this.$state.events.data && this.$state.events.data.length) {
      names = _.chain(this.$state.data.eventIndicators)
               .map(p => {
                 let eventName = _.find(this.$state.events.data, r => r.value === p.event).label;

                 let defaultContainer = defaultEventIndicatorItems.get('options');
                 let defaultIndicators = _.chain(p.indicators)
                                          .filter(r => !r.subValue)
                                          .map(r => defaultContainer.find(s => s.get('value') === r.value))
                                          .map(r => ({
                                            name: `event.${ p.event }.${ r.get('value') }`,
                                            text: `${ eventName }的${ r.get('label') }`
                                          }))
                                          .value();

                 let properties = this.$state.properties[p.event].data.getIn(['original', p.event]);
                 let extendedIndicators = _.chain(p.indicators)
                                           .filter(r => r.subValue)
                                           .map(r => {
                                             let values = r.value.split('.');
                                             let value = _.last(values);
                                             let obj = properties.get('event').find(s => s.get('name') === value);

                                             return {
                                               name: `event.${ p.event }.${ obj.get('name') }`,
                                               text: `${ eventName }的${ r.getDisplayText(obj.get('cname')) }`
                                             };
                                           })
                                           .value();

                 let data = _.map(defaultIndicators.concat(extendedIndicators), r => ({
                   identity: _.last(r.name.split('.')),
                   r
                 }));
                 let originalIdentities = _.chain(p.indicators)
                                           .map(r => _.last(r.value.split('.')))
                                           .map(r => _.find(data, v => v.identity === r))
                                           .filter(r => r)
                                           .map(r => r.r)
                                           .value();
                 return originalIdentities;
               })
               .flatten(true)
               .value();
    }
    this.$state.basicData.eventIndicatorNames = names;

    return measures;
  } // getEventIndicator

  getViewDimension() {
    let parameter = {};

    let fields = _.chain(this.$state.data.viewDimensions)
                  .filter(p => p.property !== '-1');
    parameter.by_fields = fields.map(p => p.property).value();

    let numberSettings = fields.filter(p => p. settings && p.settings.segmentType !== 1)
                               .map(p => [p.property, p.settings.values])
                               .object()
                               .value();
    parameter.bucket_params = numberSettings;

    let viewDimensions = fields.map(p => p.object.cname).value();
    if (viewDimensions.length) {
      this.$state.chart0Settings.viewDimensions = `按${ viewDimensions.join('，') }`;
    } else {
      this.$state.chart0Settings.viewDimensions = '';
    }

    let dic = fields.map(p => [p.property, p.object.cname])
                    .object()
                    .value();
    this.$state.basicData.viewDimensionDic = dic;

    return parameter;
  } // getViewDimension

  getFilterCondition() {
    if (this.$state.data.filterConditions) {
      let filters = _.chain(this.$state.data.filterConditions.filters)
                     .filter(p => is(p.filter, nullFilterValue) || (p.filter && p.filter.length && _.every(p.filter, r => r)));

      let conditions = filters.map(p => ({
                                field: p.field,
                                function: p.func,
                                params: is(p.filter, nullFilterValue) ? [] : p.filter
                              }))
                              .value();

      return {
        relation: this.$state.data.filterConditions.relation,
        conditions
      };
    } else {
      return {};
    }
  } // getFilterCondition

  fetchLineChartData(parameter) {
    if (this.__isFetchingLineData) {
      return null;
    }

    return request2({
      url: `sd/${ BlueWare.appId }/api/events/report/?bookmarkId=${ this.$state.favoriteId ? this.$state.favoriteId : '' }`,
      method: 'post',
      body: _.extend({}, parameter, {
        request_id: Date.now()
      })
    })
    .progress(() => {
      this.__isFetchingLineData = true;
      this.$state.fetchedData.grid = { loading: true };

      if (this.$state.chart0Settings.showChart0) {
        this.$state.chart0 = this.$state.fetchedData.grid;
      } else {
        this.$state.chart0 = { data: [] };
      }

      this.emit();
    })
    .fail(error => {
      this.$state.fetchedData.grid = { error };
      if (this.$state.chart0Settings.showChart0) {
        this.$state.chart0 = { error: error.responseText };
      }
    })
    .done(data => {
      let noContent = false;
      if (_.isArray(data)) {
        noContent = data[0].num_rows === 0;
      } else {
        noContent = data.num_rows === 0;
      }

      if (noContent) {
        this.$state.chart0 = { data: { series: [] } };
        this.$state.fetchedData.grid = { data: {} };

        this.ep.emit('line');
        return;
      }

      if (_.isArray(data)) {
        data = this.additionalPoints(data);
      }

      if (_.isArray(data)) {
        let list = fromJS(data);
        this.$state.fetchedData.grid = {
          data: list.get(0).toJS(),
          data1: list.get(1).toJS()
        };
      } else {
        this.$state.fetchedData.grid = { data: fromJS(data).toJS() };
      }

      if (this.$state.chart0Settings.showChart0) {
        if (_.isArray(data)) {
          let series = data[0].series.length > data[1].series.length ? data[0].series : data[1].series;
          let originalSuffix = ` - ${ parameter.from_date } 至 ${ parameter.to_date }`;
          let original = this.chartDataConvert(_.extend({}, data[0], {
            series,
            labelSeries: data[0].series
          }), originalSuffix);
          let comparerSuffix = ` - ${ parameter.compare_from_date } 至 ${ parameter.compare_to_date } `;
          let comparer = this.chartDataConvert(_.extend({}, data[1], {
            series,
            labelSeries: data[1].series
          }), comparerSuffix, 'ShortDot', data[0].rows.length);

          if (original.series[0].data.length !== comparer.series[0].data.length) {
            let result = original.series[0].data.length > comparer.series[0].data.length ? original : comparer;
            let tmp = original.series[0].data.length < comparer.series[0].data.length ? original : comparer;
            result.series = result.series.concat(tmp.series);
            this.$state.chart0 = { data: result };
          } else {
            original.series = original.series.concat(comparer.series);
            this.$state.chart0 = { data: original };
          }
        } else {
          this.$state.chart0 = {
            data: this.chartDataConvert(_.extend({}, data, {
              labelSeries: data.series
            }))
          };
        }

        this.$state.chart0._series = fromJS(this.$state.chart0.data.series);
      }

      this.ep.emit('line');
    })
    .always(() => {
      this.__isFetchingLineData = false;
      this.update(0);
    });
  } // fetchLineChartData

  getDateStart(value) {
    if (this.$state.chart0Settings.dateType === 'month') {
      return moment(value).startOf('month').valueOf();
    }
    if (this.$state.chart0Settings.dateType === 'week') {
      let weekDay = moment(value).days();
      weekDay = weekDay === 0 ? 6 : (weekDay - 1);

      return moment(value).subtract(weekDay, 'days').valueOf();
    }

    return value;
  }

  additionalPoints(data) {
    if (data[0].series.length === data[1].series.length) {
      return data;
    }

    let parameter = this.immutableParameter.toJS()
      , timeDifference = moment(parameter.to_date).valueOf() - moment(parameter.compare_to_date).valueOf()
      , leftSeries = _.map(data[0].series, p => moment(p).valueOf())
      , rightSeries = _.map(data[1].series, p => moment(p).valueOf() + timeDifference)
      , list = fromJS(data).toJS()
      , valueLength = this.$state.basicData.eventIndicatorNames.length
      , filler = []
      , duration = 0;

    for (var i = 0; i < valueLength; i++) {
      filler.push(null);
    }

    switch(this.$state.chart0Settings.dateType) {
      case 'hour':
        duration = moment.duration(1, 'hours').valueOf();
      break;
      case 'day':
        duration = moment.duration(1, 'days').valueOf();
      break;
      case 'month':
        duration = moment.duration(1, 'months').valueOf();
      break;
      default:
        duration = moment.duration(1, 'weeks').valueOf();
      break;
    }

    // leftSeries = [3, 4, 5]
    // rightSeries = [1, 2, 3, 4]
    while (this.getDateStart(_.first(leftSeries)) > this.getDateStart(_.first(rightSeries))) {
      leftSeries.unshift(_.first(leftSeries) - duration);

      _.each(list[0].rows, row => row.values.unshift(filler));
    }

    // leftSeries = [4, 5, 6]
    // rightSeries = [6, 7, 8]
    while (this.getDateStart(_.first(leftSeries)) < this.getDateStart(_.first(rightSeries))) {
      rightSeries.unshift(_.first(rightSeries) - duration);

      _.each(list[1].rows, row => row.values.unshift(filler));
    }

    // leftSeries = [3, 4, 5]
    // rightSeries = [4, 5, 6, 7]
    while (this.getDateStart(_.last(leftSeries)) < this.getDateStart(_.last(rightSeries)) && this.getDateStart(_.last(leftSeries) + duration) < this.getDateStart(Date.now())) {
      leftSeries.push(_.last(leftSeries) + duration);

      _.each(list[0].rows, row => row.values.push(filler));
    }

    // leftSeries = [7, 8, 9]
    // rightSeries = [4, 5, 6]
    while (this.getDateStart(_.last(leftSeries)) > this.getDateStart(_.last(rightSeries)) && this.getDateStart(_.last(rightSeries) + duration) < this.getDateStart(Date.now())) {
      rightSeries.push(_.last(rightSeries) + duration);

      _.each(list[1].rows, row => row.values.push(filler));
    }

    let leftSeriesFormatted = _.map(leftSeries, p => moment(p).format('YYYY-MM-DD HH:mm:ss'))
      , rightSeriesFormatted = _.map(rightSeries, p => moment(p - timeDifference).format('YYYY-MM-DD HH:mm:ss'));

    list[0].series = leftSeriesFormatted;
    list[1].series = rightSeriesFormatted;

    return list;
  } // additionalPoints

  fetchPieChartData(parameter) {
    if (this.__isFetchingPieData) {
      return null;
    }

    request2({
      url: `sd/${ BlueWare.appId }/api/events/report/?bookmarkId=${ this.$state.favoriteId ? this.$state.favoriteId : '' }`,
      method: 'post',
      body: _.extend({}, parameter, {
        rollup_date: true,
        request_id: Date.now()
      })
    })
    .progress(() => {
      this.__isFetchingPieData = true;
      this.$state.fetchedData.sum = { loading: true };

      if (this.$state.chart0Settings.showChart0) {
        this.$state.chartPie0 = this.$state.fetchedData.sum;
      } else {
        this.$state.chartPie0 = { data: [] };
      }

      this.emit();
    })
    .fail(error => {
      this.$state.fetchedData.sum = { error };
      if (this.$state.chart0Settings.showChart0) {
        this.$state.chartPie0 = { error: error.responseText };
      }
    })
    .done(data => {
      let noContent = false;
      if (_.isArray(data)) {
        noContent = data[0].num_rows === 0;
      } else {
        noContent = data.num_rows === 0;
      }

      if (noContent) {
        this.$state.chartPie0 = { data: { series: [] } };
        this.$state.fetchedData.sum = { data: {} };

        this.ep.emit('pie');
        return;
      }

      if (_.isArray(data)) {
        let list = fromJS(data);
        this.$state.fetchedData.sum = {
          data: list.get(0).toJS(),
          data1: list.get(1).toJS()
        };
      } else {
        this.$state.fetchedData.sum = { data: fromJS(data).toJS() };
      }

      if (this.$state.chart0Settings.showChart0) {
        this.$state.chart0Settings.pieChart0Key += 1;
        if (_.isArray(data)) {
          this.$state.chartPie0 = {
            data: this.pieChartDataConvert(data[0], this.$state.char0Name),
            data1: this.pieChartDataConvert(data[1], this.$state.char0Name)
          };
        } else {
          this.$state.chartPie0 = {
            data: this.pieChartDataConvert(data, this.$state.char0Name)
          };
        }

        this.$state.chartPie0._series = fromJS(this.$state.chartPie0.data.series);
        if (this.$state.chartPie0.data1) {
          this.$state.chartPie0._series1 = fromJS(this.$state.chartPie0.data1.series);
        }
      }

      this.ep.emit('pie');
    })
    .always(() => {
      this.__isFetchingPieData = false;
      this.update(0);
    });
  } // fetchPieChartData

  saveToUri() {
    queryStore.set(this.immutableParameter.toJS())
              .done(hash => location.hash = `${ hash.route }?${ hash.query }`);
  } // saveToUri

  fetchData(isForce = false) {
    let parameter = {
      use_cache: true
    };

    let indicatorCount = _.chain(this.$state.data.eventIndicators)
                          .map(p => p.indicators)
                          .flatten(true)
                          .size()
                          .value();
    let viewDimensionCount = _.chain(this.$state.data.viewDimensions)
                              .filter(p => p.property !== '-1')
                              .size()
                              .value();
    this.$state.chart0Settings.showChart0 =
      indicatorCount <= 1 && viewDimensionCount <= 2;

    this.refreshChartName();
    parameter.measures = this.getEventIndicator();
    _.extend(parameter, this.getViewDimension());
    parameter.filter = this.getFilterCondition();

    parameter.unit = this.$state.chart0Settings.dateType;
    parameter.sampling_factor = this.$state.chart0Settings.percent;

    let picker = this.$state.date.picker;
    _.extend(parameter, {
      'from_date': moment(picker.endTime - picker.spanTime).format('YYYY-MM-DD'),
      'to_date': moment(picker.endTime).format('YYYY-MM-DD'),
    });
    if (this.$state.date.comparer) {
      _.extend(parameter, {
        'compare_from_date': moment(this.$state.date.comparer.endTime - this.$state.date.comparer.spanTime).format('YYYY-MM-DD'),
        'compare_to_date': moment(this.$state.date.comparer.endTime).format('YYYY-MM-DD')
      });
    }

    let immutableParameter = fromJS(parameter);
    if (is(this.immutableParameter, immutableParameter) && !isForce) {
      return;
    } else {
      this.immutableParameter = immutableParameter;
    }

    if (!this.$state.date.comparer) {
      this.$state.showEventTableType = 'current';
    }

    this.$state.dataGrid = {
      loading: true
    };

    if (this.$state.chart0Settings.showChart0) {
      this.$state.chart0 = this.$state.chartPie0 = this.$state.dataGrid;
    }

    this.ep.all(['line', 'pie'], () => {
      this.dataGridConverter();
      this.saveToUri();
    });

    this.fetchLineChartData(parameter);

    if ((this.$state.chart0Settings.showChart0 && this.$state.chart0Settings.chartType === 'pie') || this.$state.showDataGridTotal) {
      this.fetchPieChartData(parameter);
    } else {
      this.ep.emit('pie');
    }
  } // fetchData

  $load() {
    this.ep.all(['events', 'properties', 'suggests'], () => this.refresh_state());

    // this.ep.tail('events', p => console.log('events on load', Date.now(), p));
    // this.ep.tail('properties', p => console.log('properties on load', Date.now(), p));
    // this.ep.tail('suggests', p => console.log('suggests on load', Date.now(), p));

    this.fetchEvents();
    this.fetchEventIndicatorProperties(() => {
      this.update(0);
      this.ep.emit('properties');
    });
  } // $load
}
